#!/usr/b

# -------
# Imports:
# -------
import StringIO, pickle, logging, sys
from google.appengine.ext import db
from pypng import png
from copy import deepcopy

# -----------------------
class BaseImage(db.Model):
# -----------------------
    image = db.BlobProperty()
    image_width = db.IntegerProperty()
    image_height = db.IntegerProperty()
    image_format = db.StringProperty()

    # ------------
    def load(self):
    # ------------
        # Load the image into a png format
        imgIO = StringIO.StringIO(self.image)
        pngReader = png.Reader(file=imgIO)
        imgPng = pngReader.read()

        # Return the image 
        return imgPng

# --------------------
class Image(BaseImage):
# --------------------
    md5_hash = db.StringProperty()
    uploaded = db.DateTimeProperty()
    owner = db.UserProperty()

# ------------------------------
class ThresholdedImage(BaseImage):
# ------------------------------
    source_image = db.ReferenceProperty(Image)
    threshold = db.IntegerProperty()

    # ----------------
    def simplify(self):
    # ----------------
        (width, height, pixels, metadata) = self.load()
        binarify = lambda x: True if x == 255 else False 
        binarifyRow = lambda x: map(binarify, x)
        pixels = map(binarifyRow, pixels)
        
        return (width, height, pixels, metadata)

# -------------------------
class CellComplex(db.Model):	
# -------------------------
    originalImage = db.ReferenceProperty(Image, collection_name="cellcomplexentity_original_image_set")
    thresholdedImage= db.ReferenceProperty(ThresholdedImage, collection_name="cellcomplexentity_binary_image_set")
    cellComplex = db.BlobProperty()
    I = db.BlobProperty()
    R = db.BlobProperty()

    # ------------
    def load(self):
    # ------------
        return pickle.loads(self.cellComplex)

    # ---------------------------
    def renderAbsoluteToSVG(self):
    # ---------------------------
        return self.renderToSVG(renderAbsolute = True)

    # ---------------------------
    def renderRelativeToSVG(self):
    # ---------------------------
        return self.renderToSVG(renderRelative = True)


    # ------------------------------------------------------------------------------------------
    def renderToSVG(self, thinningIndices = None, renderAbsolute = False, renderRelative = False):
    # ------------------------------------------------------------------------------------------
        # Constants:
        # ---------
        vertexRadius = 2 
        scaleFactor = 8

        # Unpickle the cell complex
        zero, one, two = pickle.loads(self.cellComplex)
        I = pickle.loads(self.I)
        R = pickle.loads(self.R)

        # Uncomment these lines to print the contents of I, R out to the console
        # logging.debug('\n\nI:\n' + str(I) + '\n\n\n')
        # logging.debug('\n\nR:\n' + str(R) + '\n\n\n')

        # Scale document
        zero = [(pt[0] * scaleFactor, pt[1] * scaleFactor) for pt in zero]
        x = [z[0] for z in zero]
        y = [z[1] for z in zero]
        xMin = min(x)
        yMin = min(y) - (3)
        xMax = max(x) + (2 * scaleFactor) 
        yMax = max(y) + (2 * scaleFactor)

        # Setup the SVG document
        svg = '<svg width="100%%" height="100%%" viewBox="%i %i %i %i" version="1.1" xmlns="http://www.w3.org/2000/svg">\n' % (xMin, yMin, xMax, yMax)
        svg += '<style type="text/css">\n'
        svg += 'circle {stroke: black; stroke-width: 1; fill:black;}\n'
        svg += 'rect {stroke:black; stroke-width:1; fill: grey;}\n'
        svg += ('line {stroke: black; stroke-width: 1;}\n') if not (renderAbsolute or renderRelative)\
                else 'line{ stroke-width: 3;}\n'
        svg += '</style>\n'

        # Draw the faces (we're going to sort of cheat and draw rectangles for each face)
        svg += '\n'
        if not (renderAbsolute or renderRelative):
            for fi, face in enumerate(two):
                pts = {}
                for ei in face:
                    (v1i, v2i) = one[ei]
                    pts[v1i] = zero[v1i]
                    pts[v2i] = zero[v2i]

                # Build the rectangle
                pts = pts.values()
                x = [pt[0] for pt in pts]
                y = [pt[1] for pt in pts]
                minX = min(x)
                maxX = max(x)
                minY = min(y)
                maxY = max(y)
                rect = (minX, minY, maxX - minX, maxY - minY)
                    
                elId = 0 if thinningIndices is None else thinningIndices[2][fi]
                svg += '<rect id="%i" x="%i" y="%i" width="%i" height="%i" />' % (elId, rect[0], rect[1], rect[2], rect[3])

        # Draw the edges 
        inf = float(sys.maxint)
        svg += '\n'

        # Absolute rendering value calculations
        if renderAbsolute:
            absScore = [None if (I[1][ei] == -inf or R[1][ei] is None) else (R[1][ei] + 1) - (I[1][ei] - 1) for ei in range(len(one))]
            maxAbsScore = max(absScore) + 1
            absScore = [maxAbsScore if v is None else v for v in absScore]
            absScore = [float(v) / maxAbsScore for v in absScore]
            absScore = [int(v * 255) for v in absScore]
            logging.debug('ABS SCORE:\n' + str(absScore))

        # Relative rendering value calculations
        elif renderRelative:
            relScore = [None if (I[1][ei] == -inf or R[1][ei] is None) else 1 - ((I[1][ei] - 1)/(R[1][ei] + 1)) for ei in range(len(one))]
            maxRelScore = max(relScore) + 1
            relScore = [maxRelScore if v is None else v for v in relScore]
            relScore = [float(v) / maxRelScore for v in relScore]
            relScore = [int(v * 255) for v in relScore]

        for ei, edge in enumerate(one):
            pt1 = zero[edge[0]]
            pt2 = zero[edge[1]]

            elId = 0 if thinningIndices is None else thinningIndices[1][ei]
            if renderAbsolute:
                # Comput the edge color based on the absolute score
                v = absScore[ei]

                # Add the line
                svg += '<line id="%i" x1="%i" y1="%i" x2="%i" y2="%i" style="stroke: rgb(%i, %i, %i)" />'\
                         % (elId, pt1[0], pt1[1], pt2[0], pt2[1], v, 255 - v, 0)
            elif renderRelative:
                # Compute the color based on the relative score
                v = relScore[ei]

                # Add the line
                svg += '<line id="%i" x1="%i" y1="%i" x2="%i" y2="%i" style="stroke: rgb(%i, %i, %i)" />'\
                         % (elId, pt1[0], pt1[1], pt2[0], pt2[1], v, 255 - v, 0)
            else:
                svg += '<line id="%i" x1="%i" y1="%i" x2="%i" y2="%i" />' % (elId, pt1[0], pt1[1], pt2[0], pt2[1])

        # Draw the vertices as circles
        svg += '\n'
        if not (renderAbsolute or renderRelative):
            for pi, pt in enumerate(zero):
                elId = 0 if thinningIndices is None else thinningIndices[0][pi]
                svg += '<circle id="%i" cx="%i" cy="%i" r="%i" />' % (elId, pt[0], pt[1], vertexRadius)

        # Close the SVG document
        svg += '</svg>'

        return svg

    # -------------------------
    def computeDegree(self, cc):
    # -------------------------
        """ Computes a score of the number of parents for each cell """
        out = [[0 for temp in range(len(row))] for row in cc]        

        # Loop through our cell rows, high --> low 
        for k in range(len(cc) - 1, 0, -1):
            row = cc[k]            

            # Loop through each cell on the row
            for cell in row:
        
                # Loop through each element in the cell
                for el in cell:
                    
                    # Increment the counter for that element
                    out[k-1][el] += 1

        return out

    # ----------------------------------------
    def updateDegrees(self, cc, deg, toRemove):
    # ----------------------------------------
        y = toRemove[0]
        x = toRemove[1]
    
        if y > 0 and cc[y][x] != 'DEL':
            for i in cc[y][x]:
                deg[y - 1][i] -= 1
        
        #deg[y][x] = 'DEL'
    
    # ------------------------------------------------------------------------------
    def thinIt(self, cellComplex, deg, I, K, thinningIndices, it, thresholds = None):
    # ------------------------------------------------------------------------------
        # Initializations
        out = deepcopy(deg)
        thinned = False
        cc = deepcopy(cellComplex)

        # Loop through each row of degrees
        for k in range(len(cc) - 1, 0, -1):
            for j in range(len(deg[k])):
               # If the cell is an end cell...
                if deg[k][j] == 0:
                
                    # Look for a witness cell 
                    cell = cellComplex[k][j]

                    if cell != 'DEL':
                        for i in cell:
                            if deg[k-1][i] == 1:
                                # Then we have a witness!
                                absVal = K - I[k][j];
                                relVal = 1 - (I[k][j] / K); 

                                # One Cell
                                #if thresholds is not None and k == 1 and absVal > thresholds[0][0] and relVal > thresholds[0][1]:
                                if thresholds is not None and k == 1 and absVal > thresholds[1] and relVal > thresholds[0]:
                                    continue

                                # Two Cell
                                #if thresholds is not None and k == 2 and absVal > thresholds[1][0] and relVal > thresholds[1][1]:
                                #    continue
                                
                                # Remove the simple cell
                                self.updateDegrees(cellComplex, out, (k,j))
                                cc[k][j] = 'DEL'
                                thinningIndices[k][j] = it
                            
                                # Remove the witness cell
                                self.updateDegrees(cellComplex, out, (k-1, i))
                                cc[k - 1][i] = 'DEL'
                                thinningIndices[k - 1][i] = it

                                thinned = True
                                break
                           
        return (cc, out, thinned, thinningIndices)

    # -----------------------------------
    def thin(self, cc, thresholds = None):
    # -----------------------------------
        # Compute the degrees of the cell complex
        deg = self.computeDegree(cc)

        # Initialize I
        I = [[-sys.maxint for i in range(len(row))] for row in cc]
        for k in range(len(cc)):
            for j in range(len(cc[k])):
    
                # If k,j is "isolated"
                if deg[k][j] == 0:

                    # Set I = 1
                    I[k][j] = 1

        # Initialize everything else
        K = 1 
        thinned = True
        resCC = deepcopy(cc)
        iteration = 0
        R = [[None for cell in row] for row in cc]

        # Loop until we stop making changes to the cell complex
        while thinned:
            logging.debug('Thinning iteration #%i' % iteration) 

            # Thin once
            resCC, resDeg, thinned, R = self.thinIt(resCC, deg, I, K, R, iteration, thresholds)

            # Set I(X) = K for newly isolated cells
            K += 1
            for k in range(len(cc)):
                for j in range(len(cc[k])):
                    if resDeg[k][j] == 0 and deg[k][j] != 0:
                        # Set I = K
                        I[k][j] = K
                        
            deg = resDeg
            iteration += 1

        # Instead of rebuilding, we can just return the thinned cell complex
        return R, I
       
    # ------------------------------
    def rebuildCellComplex(self, cc): 
    # ------------------------------
        out = [[] for row in cc]
        count = [[-1 for temp in range(len(row))] for row in cc]        
        
        # Loop over the rows, shift everything, then update the indices
        for r in range(len(cc)):
            # Initializations
            row = cc[r]
            ctr = 0

            # Loop over the cell in the row
            for c in range(len(row)):

                # Only examine cells that haven't been deleted
                if row[c] != 'DEL':
                
                    cell = list(row[c])
                
                    # Fix the indices
                    if r > 0:
                        for i in range(len(cell)):
                            cell[i] = count[r-1][cell[i]]
                    
                    # Shift 
                    out[r].append(cell)
                    count[r][c] = ctr
                    ctr += 1
           
        return out    
    
# --------------------------------
class ThinnedCellComplex(db.Model):
# --------------------------------
    source = db.ReferenceProperty(CellComplex)
    R = db.BlobProperty()
    max_it = db.IntegerProperty()
    I = db.BlobProperty()

    # -------------------
    def renderToSVG(self):
    # -------------------
        # Load the thinning indices
        thinningIndices = pickle.loads(self.R)

        return self.source.renderToSVG(thinningIndices)

